import SwiftLintCore
import SwiftSyntax

@SwiftSyntaxRule(optIn: true)
struct MultilineCallArgumentsRule: Rule {
    var configuration = MultilineCallArgumentsConfiguration()

    static let description = RuleDescription(
        identifier: "multiline_call_arguments",
        name: "Multiline Call Arguments",
        description: "Call should have each parameter on a separate line",
        kind: .style,
        nonTriggeringExamples: [
            Example("""
                foo(
                param1: "param1",
                    param2: false,
                    param3: []
                )
                """,
                configuration: ["max_number_of_single_line_parameters": 2]),
            Example("""
                foo(param1: 1,
                    param2: false,
                    param3: [])
                """,
                configuration: ["max_number_of_single_line_parameters": 1]),
            Example(
                "foo(param1: 1, param2: false)",
                configuration: ["max_number_of_single_line_parameters": 2]),
            Example(
                "Enum.foo(param1: 1, param2: false)",
                configuration: ["max_number_of_single_line_parameters": 2]),
            Example("foo(param1: 1)", configuration: ["allows_single_line": false]),
            Example("Enum.foo(param1: 1)", configuration: ["allows_single_line": false]),
            Example(
                "Enum.foo(param1: 1, param2: 2, param3: 3)",
                configuration: ["allows_single_line": true]),
            Example("""
                foo(
                    param1: 1,
                    param2: 2,
                    param3: 3
                )
                """,
                configuration: ["allows_single_line": false]),
        ],
        triggeringExamples: [
            Example(
                "↓foo(param1: 1, param2: false, param3: [])",
                configuration: ["max_number_of_single_line_parameters": 2]),
            Example(
                "↓Enum.foo(param1: 1, param2: false, param3: [])",
                configuration: ["max_number_of_single_line_parameters": 2]),
            Example("""
                ↓foo(param1: 1, param2: false,
                        param3: [])
                """,
                configuration: ["max_number_of_single_line_parameters": 3]),
            Example("""
                ↓Enum.foo(param1: 1, param2: false,
                        param3: [])
                """,
                configuration: ["max_number_of_single_line_parameters": 3]),
            Example("↓foo(param1: 1, param2: false)", configuration: ["allows_single_line": false]),
            Example("↓Enum.foo(param1: 1, param2: false)", configuration: ["allows_single_line": false]),
        ]
    )
}

private extension MultilineCallArgumentsRule {
    final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
        override func visitPost(_ node: FunctionCallExprSyntax) {
            if containsViolation(parameterPositions: node.arguments.map(\.positionAfterSkippingLeadingTrivia)) {
                violations.append(node.calledExpression.positionAfterSkippingLeadingTrivia)
            }
        }

        private func containsViolation(parameterPositions: [AbsolutePosition]) -> Bool {
            var numberOfParameters = 0
            var linesWithParameters: Set<Int> = []
            var hasMultipleParametersOnSameLine = false

            for position in parameterPositions {
                let line = locationConverter.location(for: position).line

                if !linesWithParameters.insert(line).inserted {
                    hasMultipleParametersOnSameLine = true
                }

                numberOfParameters += 1
            }

            if linesWithParameters.count == 1 {
                guard configuration.allowsSingleLine else {
                    return numberOfParameters > 1
                }

                if let maxNumberOfSingleLineParameters = configuration.maxNumberOfSingleLineParameters {
                    return numberOfParameters > maxNumberOfSingleLineParameters
                }

                return false
            }

            return hasMultipleParametersOnSameLine
        }
    }
}
